﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace game
{
    public abstract class ParticleSystem
    {
        public float MinInitialSpeed { get; set; }
        public float MaxInitialSpeed { get; set; }

        public float MinInitialSize { get; set; }
        public float MaxInitialSize { get; set; }

        public float MinLifeTimeMs { get; set; }
        public float MaxLifeTimeMs { get; set; }

        protected int elapsedSystemTimeMs;
        public float Acceleration { get; set; }


        protected Matrix rotation;
        protected Vector3 position;
        protected Vector3 velocity;

        protected short maxParticles;

        protected Scene scene = null;

        protected String textureName;
        protected String effectName;
        protected String effectTechName;

        protected Texture texture;

        protected Effect effect;
        protected EffectTechnique effectTechnique;
        protected EffectParameter ep_gVP;
        protected EffectParameter ep_gTex;
        protected EffectParameter ep_gEyePosW;
        protected EffectParameter ep_gAccel;
        protected EffectParameter ep_gPSystemTime;

        public virtual bool IsAlive { get { return true; } }

        protected int firstFreeParticleIndex;
        protected int firstActiveParticleIndex;

        protected ParticleEmitter emitter;

        protected Color color;

        protected BoundingSphere bSphere;

        public virtual BoundingSphere GetBSphereTransformed()
        {
            return bSphere;
        }

        #region Effect

        protected virtual void SetEffectParamHandles()
        {
            ep_gVP = effect.Parameters["gVP"];
            ep_gTex = effect.Parameters["gTex"];
            ep_gEyePosW = effect.Parameters["gEyePosW"];
            ep_gAccel = effect.Parameters["gAccel"];
            ep_gPSystemTime = effect.Parameters["gPSystemTime"];
        }

        protected virtual void SetEffectParams()
        {
            Camera camera = scene.ActiveCamera;
            ep_gVP.SetValue(camera.View * camera.Projection);
            ep_gEyePosW.SetValue(camera.Position);
            ep_gPSystemTime.SetValue(elapsedSystemTimeMs);
            ep_gAccel.SetValue(Acceleration);
            ep_gTex.SetValue(texture);
        }

        #endregion


        #region constructor

        public ParticleSystem(Scene scene, Vector3 position)
        {
            this.scene = scene;
            this.position = position;
            this.elapsedSystemTimeMs = 0;

            firstFreeParticleIndex = 0;
            firstActiveParticleIndex = -1;

            rotation = Matrix.CreateTranslation(position);

            velocity = new Vector3();

            bSphere = new BoundingSphere(Vector3.Zero, 1000000);

            InitParameters();
        }

        #endregion


        #region Init

        /// <summary>
        /// Called in class's constructor, should be overridden by deriving classes to supply desirable values.
        /// </summary>
        protected virtual void InitParameters()
        {
            textureName = "Textures\\Explosions\\expl2";
            effectName = "Effects\\RegularPSystem";
            effectTechName = "AdditiveBlendingSystem";

            Acceleration = 0.0f;
            maxParticles = 100;

            emitter = null;

            MinInitialSpeed = 0.0f;
            MaxInitialSpeed = 0.0f;
            MinInitialSize = 50;
            MaxInitialSize = 100;
            MinLifeTimeMs = 1000;
            MaxLifeTimeMs = 5000;
        }

        /// <summary>
        /// Called be methods AddParticle(), AddParticles(), ActivateAll().
        /// Should be overriden by deriving classes to supply desirable method of particle initialization.
        /// Supports position and initial time parameters, since in case of dynamic particle systems
        /// adding of particles will be very often handled by ParticleEmitter's, that calculte
        /// position and initial time appropriate for each particle and have to pass them somehow.
        /// </summary>
        /// <param name="index"></param>
        /// <param name="position"></param>
        /// <param name="velocity"></param>
        protected abstract void InitParticle(int index, Vector3 position, float initialTimeMsOffset);

        protected abstract void DeinitPaticle(int index);

        /// <summary>
        /// Called by user of the class in the appropriate moment, possibly immediately after construction.
        /// </summary>
        public abstract void Initialize();

        #endregion


        #region Adding particles

        /// <summary>
        /// Initializes given number of particles with default velocity and time offset values.
        /// </summary>
        /// <param name="count">number of particles to add</param>
        /// <returns>number of particles method was unable to add</returns>
        public abstract int AddParticles(int count);

        /// <summary>
        /// Initializes one particle with given position and starting time offset (see ParticleEmitter).
        /// It is rather of interest of derived classes representing dynamic particle systems like rocket tails
        /// or smoke generators. They will probably have ParticleEmitter object initialized, and AddParticle()
        /// will be automatically called in ParticleEmitter's Update() method
        /// </summary>
        /// <returns></returns>
        public abstract bool AddParticle(Vector3 position, float initialTimeMsOffset);

        /// <summary>
        /// Resets given number of particles with default values of position and starting time offset.
        /// Doesn't check, if any particles are currently active - it overwrites them.
        /// Usually shall be used by derived classes representing static particle systems like explosions - 
        /// ActivateAll() might be called in the moment when explosion starts.
        /// By overriding InitParticle method, other than default position and time offset values
        /// may be used.
        /// </summary>
        public abstract void SetParticles(int n);

        #endregion


        #region Update Draw

        protected float emitTimeAccumMs = 0;

        public virtual void Update(GameTime gameTime, Vector3 position, Matrix rotation, Vector3 velocity)
        {
            this.position = position;
            this.rotation = rotation;
            this.velocity = velocity;

            this.Update(gameTime);
        }

        public abstract void Update(GameTime gameTime);

        public abstract void Draw(GameTime gameTime);

        #endregion
    }
}
